namespace FileCollection
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Threading;

    public class FileQueueManager<T>
    {
        #region Variables
        private readonly Dictionary<string, StreamFileQueue<T>> queues;
        private readonly DirectoryInfo rootPath;
        [NonSerialized]
        private object syncRoot;
        #endregion

        #region Constructors
        public FileQueueManager(string rootPath)
        {
            if (string.IsNullOrEmpty(rootPath))
            {
                throw new ArgumentNullException("rootPath");
            }
               
            this.rootPath = new DirectoryInfo(rootPath);

            if (!this.rootPath.Exists)
            {
                this.rootPath.Create();
            }
                
            this.queues = new Dictionary<string, StreamFileQueue<T>>();

            foreach (var fileInfo in FileCollectionBase<Queue<Guid>, T>.FindCollections(this.rootPath.FullName, SearchOption.AllDirectories))
            {
                if (fileInfo.Directory != null)
                {
                    this.AddQueue(fileInfo.Directory.Name);
                }
            }
        }
        #endregion

        #region Properties
        public string RootPath
        {
            get { return this.rootPath.FullName; }
        }

        public virtual object SyncRoot
        {
            get
            {
                if (this.syncRoot == null)
                {
                    return Interlocked.CompareExchange(ref this.syncRoot, new object(), null);
                }

                return this.syncRoot;
            }
        }

        #endregion

        #region Public Methods
        public void RepairAll()
        {
            lock (this.SyncRoot)
            {
                foreach (var queue in this.queues.Values)
                {
                    queue.Repair();
                }
            }
        }

        public void Repair(string name)
        {
            if (string.IsNullOrEmpty(name))
            {
                   throw new ArgumentNullException("name");
            }

            var queue = this.queues[name];

            if (queue != null)
            {
                queue.Repair();
            }
        }

        public void AddQueue(string name)
        {
            if (string.IsNullOrEmpty(name))
            {
                throw new ArgumentNullException("name");
            }
        
        if (!this.Exists(name))
            {
                var queue = new StreamFileQueue<T>(this.ReturnQueuePath(name), name);
                lock (this.SyncRoot)
                {
                    this.queues.Add(name, queue);
                }
            }
        }

        public bool Exists(string name)
        {
            return this.queues.ContainsKey(name);
        }

       public void RemoveQueue(string name)
        {
            if (string.IsNullOrEmpty(name))
            {
                throw new ArgumentNullException("name");
            }

            if (this.Exists(name))
            {
                var queue = this.queues[name];

                if (queue != null)
                {
                    lock (this.SyncRoot)
                    {
                        this.queues.Remove(name);
                    }

                    queue.Clear();
                    queue.Close();
                }

                var queueDirectoryName = this.ReturnQueuePath(name);

                if (Directory.Exists(queueDirectoryName))
                {
                    Directory.Delete(queueDirectoryName, true);
                }
            }
        }

        public void Enqueue(string name, Stream stream, T info)
        {
            if (string.IsNullOrEmpty(name))
            {
                 throw new ArgumentNullException("name");
            }

            var queue = this.queues[name];

            if (queue != null)
            {
                queue.Enqueue(stream, info);
            }
        }

        public Stream Dequeue(string queueName, out T info)
        {
            if (string.IsNullOrEmpty(queueName))
            {
                throw new ArgumentNullException("queueName");
            }

            var queue = this.queues[queueName];
            info = default(T);

            if (queue != null)
            {
                return queue.Dequeue(out info);
            }

            return null;
        }

        public bool DequeueToAnotherQueue(string sourceName, string targetName)
        {
            if (string.IsNullOrEmpty(sourceName))
            {
                throw new ArgumentNullException("sourceName");
            }

            if (string.IsNullOrEmpty(targetName))
            {
                throw new ArgumentNullException("targetName");
            }

            var fromQueue = this.queues[sourceName];
            var targetQueue = this.queues[targetName];

            if (fromQueue != null && targetQueue != null)
            {
                return fromQueue.DequeueToAnotherQueue(targetQueue);
            }

            return false;
        }

        /// <summary>
        /// Get a collection with the name of the queues
        /// </summary>
        /// <returns></returns>
        public string[] GetQueueNames()
        {
            lock (this.SyncRoot)
            {
                string[] result = new string[this.queues.Count];
                int i = 0;
                foreach (string queueName in this.queues.Keys)
                {
                    result[i++] = queueName;
                }

                return result;
            }
        }

        public bool DequeueToAnotherQueue(string sourceName, StreamFileQueue<T> targetQueue)
        {
            if (string.IsNullOrEmpty(sourceName))
            {
                throw new ArgumentNullException("sourceName");
            }

            if (targetQueue == null)
            {
                throw new ArgumentNullException("targetQueue");
            }

            StreamFileQueue<T> fromQueue = this.queues[sourceName];

            if (fromQueue != null)
            {
                  return fromQueue.DequeueToAnotherQueue(targetQueue);
            }

            return false;
        }

        /// <summary>
        ///   Clear all items sourceName a queue
        /// </summary>
        /// <param name="name">Queue Name targetName Clear all items</param>
        public void Clear(string name)
        {
            if (string.IsNullOrEmpty(name))
            {
                throw new ArgumentNullException("name");
            }

            var queue = this.queues[name];

            if (queue != null)
            {
                queue.Clear();
            }
        }

        public void ClearAll()
        {
            lock (this.SyncRoot)
            {
                foreach (var queue in this.queues.Values)
                {
                    queue.Clear();
                }
            }
        }

        public void Close()
        {
            lock (this.SyncRoot)
            {
                foreach (var queue in this.queues.Values)
                {
                    queue.Close();
                }
            }
        }

        public void Close(string name)
        {
            if (string.IsNullOrEmpty(name))
            {
                throw new ArgumentNullException("name");
            }

            var queue = this.queues[name];

            lock (this.SyncRoot)
            {
                this.queues.Remove(name);
            }

            if (queue != null)
            {
                queue.Close();
            }
        }

        #endregion

        #region Private Methods

        private string ReturnQueuePath(string name)
        {
            return Path.Combine(this.rootPath.FullName, name);
        }

        #endregion
    }
}
